package fnord.network.server; 
 
import java.io.*; 
import java.util.*; 
import fnord.exception.*; 
import fnord.illuminati.*; 
import fnord.illuminati.card.*; 
import fnord.illuminati.card.winconditions.*; 
import fnord.illuminati.order.*; 
import fnord.network.calculator.*; 
 
public class Referee { 
    private Set<Player> _players; 
    private Server _server; 
    private Board _board; 
    private boolean _hasWon; 
    private Player _winner; 
    private GeneralWinCondition _gwc; 
    private Collection<GroupCard> _used; 
    private List<IlluminatiCard> _openIlluminati; 
     
    private static String _illuminatiList = "./lib/illuminatilist.dat"; 
    private static String _illuminatiList2 = "./illuminatilist.dat"; 
     
    
    /**
     * This is the constructor for Referee. It loads all the illuminati cards
     * and sets up all the initial conditions. 
     * @param s
     * @throws MapLoaderException
     */
    public Referee(Server s) throws MapLoaderException { 
        _players = new HashSet<Player>(); 
        _server = s; 
        _board = new Board(_players); 
        _hasWon = false; 
        _winner = null; 
        _gwc = new GeneralWinCondition(_board); 
        _openIlluminati = new ArrayList<IlluminatiCard>(); 
        File cardlist; 
        BufferedReader cardlistreader; 
         
        try { 
            cardlist = new File(Referee._illuminatiList); 
            cardlistreader = new BufferedReader(new FileReader(cardlist)); 
             
        } catch (FileNotFoundException e) { 
            try { 
                cardlist = new File(Referee._illuminatiList2); 
                cardlist.setReadOnly(); 
                cardlistreader = new BufferedReader(new FileReader(cardlist)); 
            } 
            catch (FileNotFoundException e2) { 
                throw new MapLoaderException("Could not find card list at \""+Referee._illuminatiList2+"\"."); 
            } 
             
        } 
        try { 
            ClassLoader cl = ClassLoader.getSystemClassLoader(); 
 
            String cardname; 
            while ((cardname=cardlistreader.readLine())!=null) { 
                Class<?> c = cl.loadClass(cardname); 
                _openIlluminati.add((IlluminatiCard) c.newInstance()); 
            } 
        } 
        catch (IOException e) { 
            throw new MapLoaderException("Error reading from card list."); 
        } catch (ClassNotFoundException e) { 
            throw new MapLoaderException("Could not find Card in card list."); 
        } catch (InstantiationException e) { 
            throw new MapLoaderException("Could not create Card."); 
        } catch (IllegalAccessException e) { 
            throw new MapLoaderException("Card constructor is not in proper scope."); 
        } 
         
//        Iterator<Player> playerIt = _players.iterator(); 
//        if(playerIt.hasNext()) { 
//            try { 
//                enactTurn(playerIt.next()); 
//            } catch (BadCardException e) { 
//                // TODO Auto-generated catch block 
//                e.printStackTrace(); 
//            } 
//        } 
    } 
     
    public void addPlayer(Player p) { 
        _players.add(p); 
    } 
     
    /** 
     * This is the enactTurn method, which recursively runs the game!!
     * 
     * IN THIS METHOD: 
     * For any given turn: 
     * do upkeep (i.e. make game state be normal) 
     * do income for the player (add their income stuffs) 
     * send new state to all players 
     * request first action 
     * check first action, then do it if it's valid 
     * if attack, go to bidding, and then complete attack 
     * send new game state to all players 
     *  
     * second action same as first 
     *  
     * request valid money transfers once, and then again if they did a first one 
     * check and complete those transfers 
     * send new game state to all players 
     * @throws BadCardException  
     *  
     */ 
    public void enactTurn(Player p) throws BadCardException { 
        _board.setCurrentPlayer(p); 
        doUpkeep(p); 
        doIncome(p); 
        checkWinConditions(); 
        if(_hasWon) { 
            _board.setWinner(_winner); 
            _board.setNotification("Player "+ _winner.id + "has won!"); 
            _server.sendState(_board); 
            return; 
        } 
        _board.setNotification("First order of Player "+ p.id + "'s turn"); 
        _server.sendState(_board); 
        Order order; 
        boolean didOrder = false; 
        boolean pass = false; 
        do { 
            order = _server.getOrder(p); 
            if(order == null) { 
                //disconnect player?? 
                 
            } 
            if(order instanceof PassOrder) { 
                pass = true; 
                break; 
            } 
            didOrder = doOrder(order); 
            if(didOrder) { 
                _server.sendState(_board); 
            } 
        } while(!didOrder); 
 
        if(_hasWon) { 
            _board.setWinner(_winner); 
            _board.setNotification("Player "+ _winner.id + "has won!"); 
            _server.sendState(_board); 
            return; 
        } 
         
        didOrder = false; 
        if(!pass) { 
            do { 
                order = _server.getOrder(p); 
                if(order == null) { 
                    //disconnect player?? 
                     
                } 
                if(order instanceof PassOrder) { 
                    pass = true; 
                    break; 
                } 
                didOrder = doOrder(order); 
                if(didOrder)  { 
                    _board.setOnlyMoneyTransfer(true); 
                    _server.sendState(_board); 
                } 
            } while(!didOrder); 
         
        }     
             
        if(_hasWon) { 
            _board.setWinner(_winner); 
            _board.setNotification("Player "+ _winner.id + "has won!"); 
            _server.sendState(_board); 
            return; 
        } 
         
        if(!pass) { 
            Order moneyOrder1 = _server.getOrder(p); 
            if(moneyOrder1 != null && moneyOrder1 instanceof MoneyTransferOrder) { 
                boolean worked = doMoneyTransfer(formatMoneyTransfer((MoneyTransferOrder)moneyOrder1)); 
                _board.setNotification("First free money transfer of Player "+p.id+"'s turn"); 
                _server.sendState(_board); 
                Order moneyOrder2 = _server.getOrder(p); 
                if(moneyOrder2 != null && moneyOrder2 instanceof MoneyTransferOrder && worked) { 
                    doMoneyTransfer(formatMoneyTransfer((MoneyTransferOrder)moneyOrder2));                 
                } 
            } 
        } 
        _board.setOnlyMoneyTransfer(false); 
        _board.setNotification("End of Player "+p.id+"'s turn"); 
        _server.sendState(_board); 
        enactTurn(p.nextPlayer()); 
    } 
     
    /**
     * this method draws a card. it also resets used values, etc.
     * @param p
     * @throws BadCardException
     */
    void doUpkeep(Player p) throws BadCardException { 
        _board.draw(p); 
        _used = new ArrayList<GroupCard>(); 
        TreeResetCalculator trc = new TreeResetCalculator(); 
        trc.traverse(p.getIlluminati()); 
    } 
     
    /**
     * does income for the player
     * @param toKeepUp
     */
    void doIncome(Player toKeepUp) { 
        IncomeCalculator ic = new IncomeCalculator(); 
        ic.traverse(toKeepUp.getIlluminati()); 
        checkWinConditions(); 
    } 
     
    //returns true if the order was enacted, otherwise false 
    boolean doOrder(Order o) { 
        if(o instanceof AttackOrder) { 
            AttackOrder realOrder = formatAttack((AttackOrder)o); 
            return doAttack(realOrder); 
        } 
        else if(o instanceof MoneyTransferOrder) { 
            MoneyTransferOrder realOrder = formatMoneyTransfer((MoneyTransferOrder)o); 
            return doMoneyTransfer(realOrder); 
        } 
        else if(o instanceof TreeTransferOrder) { 
            TreeTransferOrder realOrder = formatTreeTransfer((TreeTransferOrder)o); 
            return doTreeTransfer(realOrder); 
        } 
        else { 
            return false; 
        } 
        //after any order, check the win conditions 
    } 
     
    /**
     * this family of methods matches cards and players sent over the network
     * to ones in the backend's version of the board. This is so that == and the like
     * actually do stuff, as well as when we send the new state, it is actually reflected.
     * @param o
     * @return
     */
    private MoneyTransferOrder formatMoneyTransfer(MoneyTransferOrder o) { 
        Player realPlayer = null; 
        for(Player p : _players) { 
            if(p.sameAs(o.getPlayer())) { 
                realPlayer = p; 
                break; 
            } 
        } 
        GroupCard from, to; 
        from = _board.getAllGroupCards().get(o.getFrom().getId()); 
        to = _board.getAllGroupCards().get(o.getTo().getId()); 
         
        MoneyTransferOrder toReturn = new MoneyTransferOrder(realPlayer, from, to, o.getMoney()); 
         
        return toReturn; 
         
    } 
    
    private TreeTransferOrder formatTreeTransfer(TreeTransferOrder o) { 
        Player realPlayer = null; 
        for(Player p : _players) { 
            if(p.sameAs(o.getPlayer())) { 
                realPlayer = p; 
                break; 
            } 
        } 
        GroupCard from, to; 
        from = _board.getAllGroupCards().get(o.getFrom().getId()); 
        to = _board.getAllGroupCards().get(o.getTo().getId()); 
         
        TreeTransferOrder toReturn = new TreeTransferOrder(realPlayer, from, to, o.getToConnection()); 
         
        return toReturn; 
    } 
 
    private AttackOrder formatAttack(AttackOrder o) { 
        Player realPlayer = null; 
        for(Player p : _players) { 
            if(p.sameAs(o.getPlayer())) { 
                realPlayer = p; 
                break; 
            } 
        } 
        GroupCard from, to; 
        from = _board.getAllGroupCards().get(o.getFrom().getId()); 
//        System.out.println(o.getFrom().getId()); 
//        System.out.println(from); 
        to = _board.getAllGroupCards().get(o.getTo().getId()); 
         
        ArrayList<GroupCard> tPowerGroups = new ArrayList<GroupCard>(); 
        if(o.getTPowerGroups() != null) { 
            for(GroupCard tc : o.getTPowerGroups()) { 
                tPowerGroups.add(_board.getAllGroupCards().get(tc.getId())); 
            } 
        } 
         
        AttackOrder toReturn = new AttackOrder(realPlayer, from, to, o.getAttackType(), o.getTransferablePower(), tPowerGroups, o.getConnection()); 
         
        return toReturn; 
    } 
     
    private SpendMoneyOrder formatSpendMoney(SpendMoneyOrder o) { 
        Player realPlayer = null; 
        for(Player p : _players) { 
            if(p.sameAs(o.getPlayer())) { 
                realPlayer = p; 
                break; 
            } 
        } 
        GroupCard from, to; 
        from = _board.getAllGroupCards().get(o.getFrom().getId()); 
        to = _board.getAllGroupCards().get(o.getTo().getId()); 
         
        SpendMoneyOrder toReturn = new SpendMoneyOrder(realPlayer, from, to, o.getMoney()); 
         
        return toReturn; 
    } 
 
    
    /**
     * This method enacts an attack! first it makes sure it's valid, and then
     * does it. it calculates values, and then requests money from each player
     * @param ao
     * @return
     */
    boolean doAttack(AttackOrder ao) { 
        boolean isValid = true; 
        if(_used.contains(ao.getFrom())) 
            isValid = false; 
        for(GroupCard tpc : ao.getTPowerGroups()) { 
            if(_used.contains(tpc)) { 
                isValid = false; 
                break; 
            } 
        } 
        if(ao.getAttackType() == AttackType.CONTROL && !ao.getFrom().isConnectionOpen(ao.getConnection())) { 
            isValid = false; 
        } 
        if(!isValid) { 
            return false; 
        } 
        AttackCalculator attkCalc = new AttackCalculator(ao.getPlayer().getIlluminati(), ao.getFrom(), ao.getTo(), ao); 
        DefenseCalculator defCalc = new DefenseCalculator(ao.getTo(), ao.getFrom().getAlignments(), ao.getAttackType(), ao); 
        int moneyModifier = 0; 
        _board.setAttackingCard(ao.getFrom()); 
        _board.setDefendingCard(ao.getTo()); 
        //MONEY SPENDING STUFF HERE: 
        _board.setOnlySpendMoney(true); 
        Set<Player> done = new HashSet<Player>(); 
        Player spending = ao.getPlayer().nextPlayer(); 
        _board.setCurrentPlayer(spending); 
        //System.out.println(_board.getAttackingCard()); 
        _board.setNotification("Player "+ao.getPlayer().id+" is attacking "+ao.getTo().getName()+" with "+ao.getFrom().getName()); 
        _server.sendState(_board); 
        while(done.size() < _players.size()) { 
            if(!done.contains(spending)) { 
                Order o = _server.getOrder(spending); 
                if(o != null && o instanceof SpendMoneyOrder) { 
                    SpendMoneyOrder spo = formatSpendMoney((SpendMoneyOrder) o); 
                    if(spo.getMoney() > 0) { 
                        if(spo.getTo() == ao.getTo()) { 
                            //we're spending money on the defender 
                            try { 
                                spo.getFrom().removeMoney(spo.getMoney()); 
                                moneyModifier -= spo.getMoney(); 
                                if(spo.getFrom() == spo.getTo()) { 
                                    //defender is spending money for his own defense 
                                    moneyModifier -= spo.getMoney(); 
                                } 
                            } catch (MoneyException e) { 
                                done.add(spending); 
                                spending = spending.nextPlayer(); 
                                continue; 
                            } 
                             
                        } 
                        else if(spo.getTo() == ao.getFrom()) { 
                            try { 
                                spo.getFrom().removeMoney(spo.getMoney()); 
                                moneyModifier += spo.getMoney(); 
                            } catch (MoneyException e) { 
                                done.add(spending); 
                                spending = spending.nextPlayer(); 
                                continue; 
                            } 
                        } 
                    } 
                    else { 
                        done.add(spending); 
                    } 
                } 
                else { 
                    done.add(spending); 
                } 
            } 
            spending = spending.nextPlayer(); 
            _board.setCurrentPlayer(spending); 
            if(done.size() == _players.size()) { 
                _board.setCurrentPlayer(ao.getPlayer()); 
                _board.setOnlySpendMoney(false); 
            } 
            _server.sendState(_board); 
        } 
        _board.setCurrentPlayer(ao.getPlayer()); 
        _board.setOnlySpendMoney(false); 
         
        int difference  = attkCalc.getAttackPower() + ao.getTransferablePower() + moneyModifier - defCalc.getDefenseValue(); 
        Random dice = new Random(); 
        int roll = dice.nextInt(6) + dice.nextInt(6) + 2; 
        //roll = 0; 
        //System.out.println("difference is "+difference); 
        //System.out.println("roll is "+roll); 
        //_server.sendState(_board); 
        _board.setRecentRoll(roll); 
        if(roll <= difference && roll <= 10) { 
            //TODO: attach cards appropriately 
            switch(ao.getAttackType()) { 
            case NEUTRALIZE: 
                Collection<GroupCard> detached = ao.getTo().detachAll(); 
                for(GroupCard gc : detached) { 
                    _board.addToField(gc); 
                } 
                break; 
            case CONTROL: 
                if(ao.getFrom().isConnectionOpen(ao.getConnection())) { 
                    if(_board.getField().contains(ao.getTo())) { 
                        _board.getField().remove(ao.getTo()); 
                    } 
                    ao.getTo().transferTo(ao.getFrom(), ao.getConnection()); 
                } 
                else { 
                    return false; 
                } 
                break; 
            case DESTROY: 
                Collection<GroupCard> detach = ao.getTo().detachAll(); 
                detach.remove(ao.getTo()); 
                for(GroupCard gc : detach) { 
                    _board.addToField(gc); 
                } 
                // recursively return child cards to the field. 
                _board.destroyCard(ao.getTo(), ao.getPlayer()); 
                break; 
            default: 
                break; 
            } 
        } 
        _used.add(ao.getFrom()); 
        ao.getFrom().setUsed(true); 
        if(ao.getTPowerGroups() != null) { 
            for(GroupCard c : ao.getTPowerGroups()) { 
                _used.add(c); 
                c.setUsed(true); 
            } 
        } 
        _board.setNotification("The attack power was "+difference+" and the roll was "+roll); 
        return true; 
    } 
 
    boolean doMoneyTransfer(MoneyTransferOrder mto) { 
        if(mto.getFrom().isAdjacent(mto.getTo())) { 
            try{ 
                mto.getFrom().transferMoneyTo(mto.getTo(), mto.getMoney()); 
                //System.out.println(mto.getFrom().get) 
                _board.setNotification(mto.getMoney()+" money was transfered from "+mto.getFrom().getName()+" to "+mto.getTo().getName()); 
            } 
            catch(MoneyException e) { 
                _server.sendErrorToPlayer(mto.getPlayer(), e.getMessage()); 
                return false; 
            } 
        } 
        else { 
            _server.sendErrorToPlayer(mto.getPlayer(), "bad money transfer order, the cards listed aren't adjacent!"); 
            return false; 
        } 
        return true; 
    } 
     
    boolean doTreeTransfer(TreeTransferOrder tto) { 
        if(tto.getTo().isConnectionOpen(tto.getToConnection())) { 
            tto.getFrom().transferTo(tto.getTo(), tto.getToConnection()); 
            _board.setNotification(tto.getFrom().getName()+" was transfered to "+tto.getTo().getName()); 
 
            return true; 
        } 
        else { 
            _server.sendErrorToPlayer(tto.getPlayer(), "The spot you are trying to attach the card to is not open!"); 
            return false; 
        } 
    } 
     
    void checkWinConditions() { 
        for(Player p : _players) { 
            try { 
                if(p.getIlluminati().getWinCondition().hasWon(p) || _gwc.hasWon(p)) { 
                    _hasWon = true; 
                    _winner = p; 
                    break; 
                } 
            } catch (UFOException e) { 
                System.err.println("this should never happen, if it does, the cards you are playing with are broken"); 
            } 
        } 
    } 
     
    //THE LUNATIC IS IN THE GRASS 
    //THE LUNATIC IS IN THE GRASS 
     
    Set<Player> getPlayers() { return _players; } 
    void setPlayers(Set<Player> p) {  
        _players = p;  
        _board.setPlayers(p); 
    } 
 
    public IlluminatiCard getNextIlluminati() { 
        Random r = new Random(); 
        int randIndex = r.nextInt(_openIlluminati.size()); 
        return _openIlluminati.remove(randIndex); 
    } 
 
    public void dropPlayer(Player p) { 
        _players.remove(p); 
        Player beforePlayer = p.nextPlayer(); 
        while(beforePlayer.nextPlayer() != p) { 
            beforePlayer = beforePlayer.nextPlayer(); 
        } 
        beforePlayer.setNextPlayer(p.nextPlayer()); 
        Collection<GroupCard> playerTree = p.getIlluminati().detachAll(); 
        playerTree.remove(p.getIlluminati()); 
        for(GroupCard c : playerTree) { 
            _board.addToField(c); 
        } 
         
        //send state? 
    } 
 
    Board getBoard() { 
        // TODO Auto-generated method stub 
        return _board; 
    } 
} 